#!/usr/bin/env coffee

import {KeySet} from './rust'
import same_bit_prefix from './same_bit_prefix.coffee'
import {sec} from './now.coffee'
import DB from '../db/sql.coffee'

class AddrSet
  constructor:->
    @li = []
    @set = KeySet.new()
    @bin = Buffer.from []

  _delete:(addr)->
    {li,set} = @
    if set.delete(addr)
      for i,pos in li
        if 0 == Buffer.compare(i,addr)
          li.splice(pos,1)
      return true
    return false

  rebuild:->
    @bin = Buffer.concat @li

  _add:(addr)->
    {li,set} = @
    if set.add(addr)
      li.push addr
      return true
    return false

  add:(addr)->
    if @_add addr
      @rebuild()

  delete:(addr)->
    if @_delete(addr)
      @rebuild()

  Object.defineProperties @prototype,
    size:
      get:->
        @li.length


export default class Kad
  constructor:(site_id, @addr, @ping)->
    @site_id = site_id
    @_db_add = DB.prepare "INSERT INTO nkn_kad (site_id,addr) VALUES(#{site_id},?) ON CONFLICT(site_id,addr) DO NOTHING"
    @_db_del = DB.prepare "DELETE FROM nkn_kad WHERE site_id=#{site_id} AND addr=?"
    @_ = [
      new AddrSet()
    ]
    @_ping = [
      0
    ]
    @depth = 16

  _add:(pos, addr)->
    {depth} = @
    set = @_[pos]
    {size} = set
    if size < depth
      if set.add(addr)
        @_db_add.run addr
        if size == depth - 1
          _ping[pos] = sec()
    else
      {_ping} = @
      now = sec()
      if now - _ping[pos] > 60
        _ping[pos] = now

        addr0 = set.li[0]
        if await ping(addr0)
          set.li.push(set.li.unshift())
        else
          if set.add(addr)
            set._delete(addr0)
            @_db_del.run addr0
            @_db_add.run addr
    return

  get:(addr)->
    pos = same_bit_prefix(addr, @addr)

    {length} = @_
    if pos >= length
      pos = length - 1

    {bin} = set = @_[pos]
    return bin

  add:(addr)->
    if 0 == Buffer.compare(addr, @addr)
      return

    self_attr = @addr
    pos = same_bit_prefix(addr, self_attr)

    {length} = @_
    if pos >= length
      pos = length - 1
      set = @_[pos]
      if set.add(addr)
        @_db_add.run addr
        if set.size >= @depth
          next = new AddrSet()
          for i in set.li
            if same_bit_prefix(i,self_attr) > pos
              set._delete(i)
              next._add(i)
            set.rebuild()
            next.rebuild()
          @_.push next
          @_ping.push 0
    else
      @_add(pos, addr)
    return
